#ifndef _HISTORY_H_
#define _HISTORY_H_ 1
#include <stack>
#include <vector>
#include <iostream>
#include <string>
#include "command.h"
#include "environ.h"
#include <algorithm>
#include <sstream>
class CommandHistory{
public:
  ~CommandHistory();
  inline void 
  undo(){
    if(undo_commands.empty()){
      std::cerr << "Nothing to undo" << std::endl;
      return;
    }
    ModifiedCommand *mcmd = undo_commands.top();
    undo_commands.pop();
    mcmd->undo();
    redo_commands.push(mcmd);
  };
  inline void 
  redo(){
    if(redo_commands.empty()){
      std::cerr << "Nothing to redo" << std::endl;
      return;
    }
    ModifiedCommand *mcmd = redo_commands.top();
    redo_commands.pop();
    mcmd->redo();
    undo_commands.push(mcmd);
  };
  inline void
  record(Command *cmd){
    ModifiedCommand *mcmd = dynamic_cast<ModifiedCommand*>(cmd);
    if(mcmd != NULL){
      undo_commands.push(mcmd);
    }
    else
      delete cmd;
  };
  inline std::string
  list(int n){
    if(n <= 0) return std::string("");;
    std::vector<ModifiedCommand*> commands = get_last_n_cmd(n);
    int num = (n <= commands.size())?n:commands.size();
    int j = 1;
    std::string result;
    for(int i = commands.size()-1;i >= commands.size() - num && i >= 0;i--){
       result +=  std::to_string(j) +  " " + commands[i]->get_action() + "\n";
       j++;
    }
    return result;
  };
  std::vector<ModifiedCommand*>
  get_last_n_cmd(int n);
private:
  std::stack<ModifiedCommand*>undo_commands;
  std::stack<ModifiedCommand*>redo_commands;
};
class LCommand : public Command{
public:
  LCommand(std::string str,Editor *editor,CommandHistory *ch,int n):Command(std::move(str),editor),history(ch),n(n){
  }
  inline std::string 
  execute(){
    return history->list(n);
  };
private:
  CommandHistory *history;
  int n;
};
class UCommand : public Command{
public:
  UCommand(std::string str,Editor *editor,CommandHistory *ch):Command(std::move(str),editor),history(ch){
  }
  inline std::string 
  execute(){
    history->undo();
    return std::string("");
  };
private:
  CommandHistory *history;
};
class RCommand : public Command{
public:
  RCommand(std::string str,Editor *editor,CommandHistory *ch):Command(std::move(str),editor),history(ch){
  }
  inline std::string 
  execute(){
    history->redo();
    return std::string("");
  };
private:
  CommandHistory *history;
};
class MDCommand : public Command{
//macro define command
public:
  MDCommand(std::string str,Editor *editor,CommandHistory *ch,std::string cmd_name,Environ *environ,int n):
    Command(std::move(str),editor),
    history(ch),
    cmd_name(std::move(cmd_name)),
    environ(environ),
    n(n)
  {
  }
  inline std::string
  execute(){
    if(environ->macros.find(cmd_name) != environ->macros.end()){
      std::cerr << "Error: Macro " << cmd_name << " has been defined!" << std::endl;
      return std::string("");
    }
    std::vector<ModifiedCommand*>cmds = history->get_last_n_cmd(n);
    environ->macros.emplace(std::pair<std::string,std::vector<ModifiedCommand*>>(cmd_name,cmds));
    return std::string("");
  }
private:
  CommandHistory *history;
  std::string cmd_name;
  Environ *environ;
  int n;
};
class MCommand : public ModifiedCommand{
//macro command
public:
  MCommand(std::string str,Editor *editor,std::vector<ModifiedCommand*> &cmds):ModifiedCommand(std::move(str),editor),cmds(cmds){
  }
protected:
  inline void
  edit(){
    for(auto cmd:cmds)
       cmd->edit();
  }
private:
  std::vector<ModifiedCommand*> &cmds;
};
#endif
